package gov.nasa.anml.lifted;

import java.util.ArrayList;

import gov.nasa.anml.*;
import gov.nasa.anml.PDDL.TimedBooleanExpression;
import gov.nasa.anml.utility.*;

//really this is a function binding

public class FluentFunctionReference<T extends SimpleObject<T>> extends Bind<FluentFunction<T>,T,T> {

	public FluentFunctionReference() {}

	public FluentFunctionReference(FluentFunction<T> ref) {
		super(ref);
	}

	// could make a pool of various arity arrays to avoid garbage collection
	// and memory bloat simultaneously	
	public T value(State s) {
		BindingHistoryMap<T> f = (BindingHistoryMap<T>) s.resolveFunction(ref.id);
		if (f == null)
			return null;
		History<T> h = f.get(setArgs(s));
		if (h == null)
			return null;
		return h.value;
	}

	public TypeCode typeCode() {
		return ref.typeCode();
	}

	public History<T> storage(State p, State c) {
		BindingHistoryMap<T> f = (BindingHistoryMap<T>) c.resolveFunction(ref.id);
		if (f == null)
			return null;
		return f.get(setArgs(p));
	}

	public boolean apply(State p, int contextID, State c) {
		if (ref.typeCode() != TypeCode.Boolean)
			return false;
		if (value(p) != ANMLBoolean.True)
			return false;
		return true;
	}



	public transient PDDL.BooleanExpression asPDDLCondition;
	public transient PDDL.PredicateReference asPDDLPredicateReference;
	public transient PDDL.FunctionReference asPDDLFunctionReference;
	public transient PDDL.Expression asPDDLExpression;

	public PDDL.FunctionReference asPDDLFloatExpr() {
		return asPDDLFunctionReference;
	}

	public PDDL.PredicateReference asPDDLBooleanExpr() {
		return asPDDLPredicateReference;
	}

	public void translateStmt(PDDL pddl, Interval unit, PDDL.Time part) {
		switch(typeCode()) {
		case Boolean:
			translateStmtB(pddl,unit,part);
			break;
		case Float:
			translateStmtF(pddl,unit,part);
			break;
		default:
			System.err.println("Oops!!");
		}

	}

	public PDDL.Expression translateExpr(PDDL pddl, Interval unit) {
		if (asPDDLExpression != null)
			return asPDDLExpression;
		switch(typeCode()) {
		case Float:
			return asPDDLExpression = translateRefF(pddl,unit);
		case Boolean:
			return asPDDLExpression = translateRefB(pddl,unit);
		default:
			System.err.println("Oh no! PDDL only supports fluent booleans and floats (symbols/objects are okay as arguments/parameters, but not as values).");
		}
		return pddl.FalseRef;
	}
	
	public PDDL.Expression translateLValue(PDDL pddl, Interval unit) {
		if (asPDDLExpression != null)
			return asPDDLExpression;
		switch(typeCode()) {
		case Float:
			return asPDDLExpression = translateRefF(pddl,unit);
		case Boolean:
			return asPDDLExpression = translateRefB(pddl,unit);
		default:
			System.err.println("Oh no! PDDL only supports fluent booleans and floats (symbols/objects are okay as arguments/parameters, but not as values).");
		}
		return super.translateLValue(pddl,unit);
	}

	private PDDL.BooleanExpression translateRefB(PDDL pddl, Interval unit) {
		if (asPDDLPredicateReference != null)
			return asPDDLPredicateReference;
		asPDDLPredicateReference = pddl.new PredicateReference(ref.boolPDDL);
		for (Expression<? extends SimpleObject<?>,?> e : this.arguments) {
			PDDL.Argument a = e.translateArgument(pddl,unit);
			if (a != null)
				asPDDLPredicateReference.arguments.add(a);
			else {
				System.err.println("Arguments must be object/symbol literals or parameters.");
			}
		}		
		return asPDDLPredicateReference;
	}
	
	private PDDL.FloatExpression translateRefF(PDDL pddl, Interval unit) {
		if (asPDDLFunctionReference != null)
			return asPDDLFunctionReference;
		asPDDLFunctionReference = pddl.new FunctionReference(ref.floatPDDL);
		for (Expression<? extends SimpleObject<?>,?> e : this.arguments) {
			PDDL.Argument a = e.translateArgument(pddl,unit);
			if (a != null)
				asPDDLFunctionReference.arguments.add(a);
			else {
				System.err.println("Arguments must be object/symbol literals or parameters.");
			}
		}
		return asPDDLFunctionReference;
	}
	
	void translateStmtB(PDDL pddl, Interval unit, PDDL.Time part) {
		ArrayList<PDDL.BooleanExpression> conditions = unit.getPDDLConditions(); 
		ArrayList<PDDL.Effect> effects = unit.getPDDLEffects();
		translateRefB(pddl,unit);
		switch(part) {
		case Start:
		case Interim:
		case End:
			conditions.add(pddl.wrap(part,asPDDLPredicateReference));
			break;
		case All:
			conditions.add(pddl.wrap(PDDL.Time.Start,asPDDLPredicateReference));
			conditions.add(pddl.wrap(PDDL.Time.Interim,asPDDLPredicateReference));
			conditions.add(pddl.wrap(PDDL.Time.End,asPDDLPredicateReference));
			break;
		case StartHalf:
			conditions.add(pddl.wrap(PDDL.Time.Start,asPDDLPredicateReference));
			conditions.add(pddl.wrap(PDDL.Time.Interim,asPDDLPredicateReference));
			break;
		case EndHalf:
			conditions.add(pddl.wrap(PDDL.Time.Interim,asPDDLPredicateReference));
			conditions.add(pddl.wrap(PDDL.Time.End,asPDDLPredicateReference));
			break;
		case Timeless:
			conditions.add(asPDDLPredicateReference);
			break;
		default:
			System.err.println("New PDDL.Time constant unaccounted for in FluentFunctionRef.");
			conditions.add(pddl.FalseCondition);
		}
	}

	void translateStmtF(PDDL pddl, Interval unit, PDDL.Time part) {
		System.err.println("Warning: Encountered numeric-expression-as-statement, i.e., an attempt at condition. Interpreting as an impossible constraint.\n\t"+this);
		unit.getPDDLConditions().add(pddl.FalseCondition);
	}


}


